#!/bin/bash

# ffmpeg-broadcaster - a shell script for broadcasting through RTMP servers
# Copyright 2013-2017 bill-auger <http://github.com/bill-auger/ffmpeg-broadcaster/issues>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


THIS_NAME=`basename "$0"`
USAGE="USAGE:
    $THIS_NAME [ a_configuration [ once ] ]

    when called with no arguments the stream runs the default configuration, automatically
        restarting after any exceptional event (device error , dropped connection , etc)
    the optional \"a_configuration\" selects a particular configuration and is one of:
        [ screen , screen+text , screen+cam , cam+text , all , break ]
    the optional second argument 'once' disables automatic restarts

    NOTE: \$RTMP_URL and \$STREAM_KEY must be defined either in this script or in the environment
    NOTE: all configurations include audio - set CAPTURE_AUDIO to 0 to disable"

# the entries under the following headings are intended for per-user configuration:
#   * user a/v params
#   * user image params
#   * user text params
#   * user text configuration
#   * user a/v configuration
#   * user logging configuration
#
# the entries under the following headings are general and suitable for most cases:
#   * general a/v params
#   * general text params
#   * general logging params
#   * general a/v configuration
#
# when one of the configurations displaying text is chosen
#     the first line of $TEXT_FILE will be rendered


# user a/v params
HOST="lctv"                          # "lctv" | ""
[ "$RTMP_URL"   ] || RTMP_URL=""     # destination URL if HOST="" (or define RTMP_URL in env  )
[ "$STREAM_KEY" ] || STREAM_KEY=""   # your private stream key    (or define STREAM_KEY in env)
SCREEN_W=1280                        # input screen width  (e.g. desktop width )
SCREEN_H=800                         # input screen height (e.g. desktop height)
SCREEN_N=':0.0'                      # display/screen number to capture (usually :0.0)
CAM_LG_W=640                         # input screen width  (e.g. desktop width )
CAM_LG_H=480                         # input screen height (e.g. desktop height)
CAM_SM_W=160                         # input screen width  (e.g. desktop width )
CAM_SM_H=120                         # input screen height (e.g. desktop height)
CAM_DEV=/dev/video0                  # video device to grab (usually /dev/video0)
STREAM_W=1280                        # input screen width  (e.g. desktop width )
STREAM_H=800                         # input screen height (e.g. desktop height)
FPS=12                               # target output FPS (12 is plenty for screen capture)
GOP=$(($FPS*4))                      # i-frame interval
CBR='1200k'                          # constant output bitrate (should be between 800k - 1300k)
CAPTURE_AUDIO=1                      # set to 1 to capture audio - 0 to disable audio
AUDIO_CHANNELS=2                     # number of channels
AUDIO_SAMPLERATE=48000               # audio samplerate (22050 is plenty for voice)
AUDIO_BITRATE='128k'                 # audio bitrate (64k per channel is plenty for voice)
ALSA_DEVICE='hw:0'                   # ALSA plug device (usually hw:0)
THREADS=0                            # 0 for auto - max 6
QUALITY='ultrafast'                  # one of the many FFMPEG presets
OUTPUT_FILE=./ffmpeg-broadcaster.flv # path for output file if STREAM_OUTPUT=$FILE_OUTPUT
EXIT_CMD=''                          # optional commands to run if unexpectedly disconnected

# user image params
[ ! -z "$STREAM_IMG" ] || STREAM_IMG="" # fullscreen interstitial image (or define STREAM_IMG in env)
[ ! -z "$LOGO_IMG"   ] || LOGO_IMG=""   # small overlay image           (or define LOGO_IMG in env)

# user text params
TEXT_FILE=/run/user/`id -u`/av-caster-text
[ "$STREAM_TEXT" ] || STREAM_TEXT="" # initial overlay text (or define STREAM_TEXT in env)
FONT_FILE=/usr/share/fonts/truetype/tlwg/Purisa.ttf
FONT_SIZE=48
TEXT_BORDER_D=4
TEXT_SHADOW_W=8
TEXT_SHADOW_H=8
BLINK_TEXT_PERIOD=300 # repeat interval in seconds
TEXT_MARQUEE_SPEED=6  # ~= n characters per second
TEXT_COLOR='white@1.0'
TEXT_BORDER_COLOR='purple@1.0'
TEXT_BG_COLOR='black@0.5'

# general a/v params
SCREEN_RES=$SCREEN_W'x'$SCREEN_H # screen capture input resolution
CAM_HI_RES=$CAM_LG_W'x'$CAM_LG_H # webcam input/output high resolution (fullscreen)
CAM_LO_RES=$CAM_SM_W'x'$CAM_SM_H # webcam input/output low resolution (overlay)
STREAM_RES=$STREAM_W'x'$STREAM_H # mix output resolution
SCREENCAP_INPUT="-f x11grab      -s $SCREEN_RES -r $FPS -i $SCREEN_N"
WEBCAM_INPUT_LG="-f video4linux2 -s $CAM_HI_RES         -i $CAM_DEV"
WEBCAM_INPUT_SM="-f video4linux2 -s $CAM_LO_RES         -i $CAM_DEV"
STATIC_INPUT_LG="-f image2       -loop 1        -r $FPS -i $STREAM_IMG"
STATIC_INPUT_SM="-f image2       -loop 1        -r $FPS -i $LOGO_IMG"
ALSA_INPUT="     -f alsa                                -i $ALSA_DEVICE"
PULSE_INPUT="    -f pulse -name $THIS_NAME -sample_rate $AUDIO_SAMPLERATE
                          -channels $AUDIO_CHANNELS     -i default"
JACK_INPUT="     -f jack  -channels $AUDIO_CHANNELS     -i $THIS_NAME"
OVERLAY_TL_POS="0: 0"
OVERLAY_BR_POS="main_w-overlay_w: main_h-overlay_h"
X264_OUTPUT="-vcodec libx264 -pix_fmt yuv420p -preset $QUALITY -s $STREAM_RES
             -g $GOP -vb $CBR -maxrate $CBR -bufsize $CBR"
AAC_OUTPUT="-acodec aac -ar $AUDIO_SAMPLERATE -ab $AUDIO_BITRATE -strict experimental"
THREADS="-threads $THREADS"
case $HOST in
   "lctv") RTMP_URL="$LCTV_RTMP_URL/$STREAM_KEY" ;;
   *     ) STREAM_KEY="unused"                   ;;
esac
FILE_OUTPUT="-f flv $OUTPUT_FILE"
LIVE_OUTPUT="-f flv $RTMP_URL"

# general image params
[ "$SELECTED_CONFIGURATION" != "interstitial" ] && STREAM_IMG='unused'

# general text params
FONT='fontfile='$FONT_FILE': fontsize='$FONT_SIZE': fontcolor='$TEXT_COLOR': shadowx='$TEXT_SHADOW_W' : shadowy='$TEXT_SHADOW_H
TEXT_BG='box=1: boxcolor='$TEXT_BG_COLOR                                       # unused
TEXT_BORDER='borderw='$TEXT_BORDER_D': bordercolor='$TEXT_BORDER_COLOR
[ ! -z "$TEXT_FILE" ] && [ -f $TEXT_FILE ] && STREAM_TEXT=`cat $TEXT_FILE`
BLINK_TEXT_DURATION=$((${#STREAM_TEXT}/$TEXT_MARQUEE_SPEED))
BLINK_TEXT=': enable=lt(mod(t\,'$BLINK_TEXT_PERIOD')\,'$BLINK_TEXT_DURATION')' # unused
STATIC_TEXT='text='$STREAM_TEXT                                                # unused
TEXT_X_CENTER_POS='x=(main_w-text_w)/2'
TEXT_Y_TOP_POS='y=0'                                                           # unused
TEXT_Y_BOTTOM_POS='y=main_h-(line_h*1.5)'
TEXT_X_MARQUEE_POS='x=main_w-((('$SCREEN_W'+text_w)/'$BLINK_TEXT_DURATION')*mod(t\,'$BLINK_TEXT_DURATION'))'$BLINK_TEXT
FILE_TEXT="textfile=$TEXT_FILE: reload=1"

# general logging params
LOGGING_NONE='-loglevel quiet'
LOGGING_ERRORS='-loglevel error'
LOGGING_VERBOSE=''

# user a/v configuration
OVERLAY_INPUT=$WEBCAM_INPUT_SM # one of $WEBCAM_INPUT_SM , $STATIC_INPUT_SM
OVERLAY_POS=$OVERLAY_BR_POS    # one of $OVERLAY_TL_POS , $OVERLAY_BR_POS
AUDIO_INPUT=$PULSE_INPUT       # one of $ALSA_INPUT , $PULSE_INPUT , $JACK_INPUT
VIDEO_OUTPUT=$X264_OUTPUT      # only $X264_OUTPUT is defined (others are possible)
AUDIO_OUTPUT=$AAC_OUTPUT       # only $AAC_OUTPUT  is defined (others are possible)
STREAM_OUTPUT=$LIVE_OUTPUT     # one of $FILE_OUTPUT , $LIVE_OUTPUT

# general a/v configuration
[ ! -z "$TEXT_FILE"  ] && [ ! -f $TEXT_FILE ] && echo "$STREAM_TEXT" > $TEXT_FILE
(($CAPTURE_AUDIO)) || (AUDIO_INPUT="" ; AUDIO_OUTPUT="")

# user text configuration
TEXT=": $FILE_TEXT"
TEXT_POS=": $TEXT_X_MARQUEE_POS: $TEXT_Y_BOTTOM_POS"
TEXT_DECORATIONS=": $TEXT_BORDER"
DRAWTEXT_PARAMS="$FONT $TEXT $TEXT_POS $TEXT_DECORATIONS"

# user logging configuration
LOGGING=$LOGGING_NONE # console output (one of $LOGGING_NONE , $LOGGING_ERRORS , $LOGGING_VERBOSE)


screenOnly()
{
  printf "\nlaunching screenOnly configuration\n\n"
  ffmpeg $SCREENCAP_INPUT \
         $AUDIO_INPUT     \
         $VIDEO_OUTPUT    \
         $AUDIO_OUTPUT    \
         $THREADS         \
         $LOGGING         \
         $STREAM_OUTPUT
  QUIT=$?
echo "QUIT=$QUIT"
}

screenPlusText()
{
  printf "\nlaunching screenPlusText configuration\n\n"
  ffmpeg $SCREENCAP_INPUT                            \
         -filter_complex drawtext="$DRAWTEXT_PARAMS" \
         $AUDIO_INPUT                                \
         $VIDEO_OUTPUT                               \
         $AUDIO_OUTPUT                               \
         $THREADS                                    \
         $LOGGING                                    \
         $STREAM_OUTPUT
  QUIT=$?
}

screenPlusCam()
{
  printf "\nlaunching screenPlusCam configuration\n\n"
  avconv $SCREENCAP_INPUT                      \
         $OVERLAY_INPUT                        \
        -filter_complex overlay="$OVERLAY_POS" \
         $AUDIO_INPUT                          \
         $VIDEO_OUTPUT                         \
         $AUDIO_OUTPUT                         \
         $THREADS                              \
         $LOGGING                              \
         $STREAM_OUTPUT
  QUIT=$?
}

camPlusText()
{
  printf "\nlaunching camPlusText configuration\n\n"
  ffmpeg $WEBCAM_INPUT_LG                            \
         -filter_complex drawtext="$DRAWTEXT_PARAMS" \
         $AUDIO_INPUT                                \
         $VIDEO_OUTPUT                               \
         $AUDIO_OUTPUT                               \
         $THREADS                                    \
         $LOGGING                                    \
         $STREAM_OUTPUT
  QUIT=$?
}

screenPlusCamPlusText()
{
  printf "\nlaunching screenPlusCamPlusText configuration\n\n"
  ffmpeg $SCREENCAP_INPUT                                                      \
         $OVERLAY_INPUT                                                        \
         -filter_complex "overlay='$OVERLAY_POS', drawtext='$DRAWTEXT_PARAMS'" \
         $AUDIO_INPUT                                                          \
         $VIDEO_OUTPUT                                                         \
         $AUDIO_OUTPUT                                                         \
         $THREADS                                                              \
         $LOGGING                                                              \
         $STREAM_OUTPUT
  QUIT=$?
}

interstitial()
{
  printf "\nlaunching interstitial configuration\n\n"
  ffmpeg $STATIC_INPUT_LG \
         $AUDIO_INPUT     \
         $VIDEO_OUTPUT    \
         $AUDIO_OUTPUT    \
         $THREADS         \
         $LOGGING         \
         $STREAM_OUTPUT
  QUIT=$?
}

launch()
{
  printf "\nstream started at `date`\n\n$QUIT_MSG" ;
  $SELECTED_CONFIGURATION
  printf "\nstream ended at `date`\n\n" ;
}


### main entry ###

# swicth on configuration
case $1 in
     'screen'     ) SELECTED_CONFIGURATION='screenOnly'            ;;
     'screen+text') SELECTED_CONFIGURATION='screenPlusText'        ;;
     'screen+cam' ) SELECTED_CONFIGURATION='screenPlusCam'         ;;
     'cam+text'   ) SELECTED_CONFIGURATION='camPlusText'           ;;
     'all'        ) SELECTED_CONFIGURATION='screenPlusCamPlusText' ;;
     'break'      ) SELECTED_CONFIGURATION='interstitial'          ;;
     *            ) SELECTED_CONFIGURATION='screenPlusCamPlusText' ;;
esac

# sanity checks
[ -z "$RTMP_URL"   ] && echo "RTMP_URL undefined"   && exit
[ -z "$STREAM_KEY" ] && echo "STREAM_KEY undefined" && exit
[ -z "$STREAM_IMG" ] && echo "STREAM_IMG undefined" && exit

# launch configuration once
QUIT_MSG="press <q> to exit"
if [ "$2" == 'once' ] ; then launch ; exit ; fi ;

# launch configuration indefinitely
QUIT_MSG="press <q> to restart the stream - press <CTRL-c> to exit"
# TODO: ffmpeg exits with 0 on server kick - need some way to trap this
# while !(($QUIT)) ; do sleep 1 ; launch ; $EXIT_CMD ; done ;
while ((1)) ; do sleep 1 ; launch ; $EXIT_CMD ; done ;
